require 'capistrano/ext/windows_server/target_system_check'

# Capistrano 1 and 2 have different configuration locations
configuration = Capistrano::Configuration.respond_to?(:instance) ?
  Capistrano::Configuration.instance(:must_exist) :
  Capistrano.configuration(:must_exist)

if TargetSystemCheck.windows?
  configuration.load do
    set :scm, :git
    set :deploy_via, :remote_cache
    set :shared_dir, "."
    set :repository_cache, "current"
    set :scm_verbose, true
    set :use_sudo, false
    set :default_shell, false

    def strip_cygdrive(path)
      path.sub /\/cygdrive\/([c-z])\//, '\1:/'
    end

    def latest_release
      strip_cygdrive current_path
    end
    
    # Probably not compatible with multiple roles with different PATHs
    def system_path
      return @system_path if @system_path
      
      @system_path = "/bin:" + "/usr/bin:"
      run('echo $PATH') { |channel, stream, data| @system_path << data.strip }
      @system_path
    end
    
    task :path do
      puts system_path.inspect
    end
    
    def run_ruby_cmd(cmd)
      bundler_git_path = "#{File.dirname(bundler_git_command)}:" rescue nil
      run "cd #{current_path} && PATH='#{bundler_git_path}#{system_path}' ruby -S #{cmd}"
    end
    
    def bundle_exec(cmd)
      if gemfile_present?
        run_ruby_cmd "bundle exec #{cmd}"
      else
        run_ruby_cmd cmd
      end
    end
    
    def application_server
      app_server rescue :mongrel
    end
    
    def gemfile_present?
      File.exists? "Gemfile"
    end

    namespace :deploy do
      desc "Update the code on the server by doing a git reset --hard <ref> in the current/ directory"
      task :update_code do
        # update_repository_cache will attempt to clean out the repository; we must prevent that.
        # /bin/git below is where www.windowsgit.com's windows git/ssh installation puts the git executable.
        # This creates a git wrapper that goes in $PATH before /bin, which will filter out any `git clean` commands.
        run "/bin/mkdir -p '#{shared_path}/bin'"
        run <<-RUN
          echo 'if [ "$1" != "clean" ]; then /bin/git $*; fi' > "#{shared_path}/bin/git.exe"
        RUN
        alter_path_cmd = "export PATH=#{shared_path}/bin:$PATH # Required for capistrano-windows-server"
        run <<-RUN
          if ! /bin/grep '#{alter_path_cmd}' ~/.bashrc > /dev/null; then echo '#{alter_path_cmd}' >> ~/.bashrc; fi
        RUN

        continue = true
        while continue
          begin
            strategy.send 'update_repository_cache'
            continue = false
          rescue
            puts "Failed to check out git. If the error was 'fatal: The remote end hung up unexpectedly', this is due to a cygwin ssh bug."
            puts "It often works to just try again repeatedly (a lot! 30+ times for an initial clone - it's like playing the lottery :-)"
            @try_again_response ||= Capistrano::CLI.ui.ask("Keep trying? [Y/n]: ").to_s.chomp
            unless @try_again_response.empty? || @try_again_response.downcase == 'y'
              continue = false
              raise $!
            end
          end
        end
        finalize_update
      end

      desc <<-DESC
        Prepares one or more servers for deployment. Before you can use any \
        of the Capistrano deployment tasks with your project, you will need to \
        make sure all of your servers have been prepared with `cap deploy:setup'. When \
        you add a new server to your cluster, you can easily run the setup task \
        on just that server by specifying the HOSTS environment variable:

        $ cap HOSTS=new.server.com deploy:setup

        It is safe to run this task on servers that have already been set up; it \
        will not destroy any deployed revisions or data.
      DESC
      task :setup do
        dirs = [deploy_to]
        run "/bin/mkdir -p #{dirs.join(' ')} && /bin/chmod g+w #{dirs.join(' ')}"

        if exists?(:repository_host_key)
          run "if ! /bin/grep '#{repository_host_key}' ~/.ssh/known_hosts > /dev/null; then echo '#{repository_host_key}' >> ~/.ssh/known_hosts; fi"
        end
      end

      # Clear out tasks that are not appropriate on Windows
      %w(finalize_update create_symlink symlink cleanup).each do |removed_task|
        task removed_task do; end
      end
      namespace :rollback do
        %w(code default).each do |removed_task|
          task removed_task do; end
        end
      end

      desc "Run pending migrations. Override the default rails environment with RAILS_ENV"
      task :migrate do
        bundle_exec "rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV'] || rails_env}"
      end

      desc "Start mongrel"
      task :start do
        return unless application_server == :mongrel
        mongrel_instances.each do |n|
          run "net start #{mongrel_instance_prefix}#{n}; true"
        end
      end

      desc "Stop mongrel"
      task :stop do
        return unless application_server == :mongrel
        mongrel_instances.each do |n|
          run "net stop #{mongrel_instance_prefix}#{n}; true"
        end
      end

      desc "Restart mongrel"
      task :restart do
        return unless application_server == :mongrel
        mongrel_instances.each do |n|
          run "net stop #{mongrel_instance_prefix}#{n}; true"
          run "net start #{mongrel_instance_prefix}#{n}; true"
        end
      end

      namespace :mongrel do
        desc "Create mongrel services"
        task :setup do
          return unless application_server == :mongrel
          mongrel_instances.each do |n|
            run "/bin/mkdir -p #{current_path}/{tmp,log}" # These are often not under version control, but their absence keeps mongrel from recognizing the rails app
            bundle_exec "mongrel_rails service::install -e #{rails_env} -N #{mongrel_instance_prefix}#{n} -p #{base_port + n - mongrel_instances.first}; true"
            run %Q(sc.exe config "#{mongrel_instance_prefix}#{n}" start= auto type= interact type= own; true)
          end
        end

        desc "Remove mongrel services"
        task :remove do
          mongrel_instances.each do |n|
            bundle_exec "mongrel_rails service::remove -N #{mongrel_instance_prefix}#{n}; true"
          end
        end
      end

      namespace :assets do
        task :symlink, :roles => :web, :except => { :no_release => true } do
        end

        desc <<-DESC
          Run the asset precompilation rake task. You can specify the full path \
          to the rake executable by setting the rake variable. You can also \
          specify additional environment variables to pass to rake via the \
          asset_env variable. The defaults are:

            set :rake,      "rake"
            set :rails_env, "production"
            set :asset_env, "RAILS_GROUPS=assets"
        DESC
        task :precompile, :roles => :web, :except => { :no_release => true } do
          run "rm -rf #{current_path}/public/#{assets_prefix}"
          bundle_exec "rake RAILS_ENV=#{rails_env} #{asset_env} assets:precompile"
        end

        desc <<-DESC
          Run the asset clean rake task. Use with caution, this will delete \
          all of your compiled assets. You can specify the full path \
          to the rake executable by setting the rake variable. You can also \
          specify additional environment variables to pass to rake via the \
          asset_env variable. The defaults are:

            set :rake,      "rake"
            set :rails_env, "production"
            set :asset_env, "RAILS_GROUPS=assets"
        DESC
        task :clean, :roles => :web, :except => { :no_release => true } do
          bundle_exec "rake RAILS_ENV=#{rails_env} #{asset_env} assets:clean"
        end
      end
    end

    desc "Run a rake command in COMMAND. RAILS_ENV for the configured environment will be included; it can be overridden by setting RAILS_ENV"
    task :rake do
      raise "Specify the command with COMMAND='some:task with_arguments'" unless ENV['COMMAND']
      bundle_exec "rake #{ENV['COMMAND']} RAILS_ENV=#{ENV['RAILS_ENV'] || rails_env}"
    end
    
    desc "Run a rubygems (gem) command in COMMAND"
    task :rubygems do
      raise "Specify the command with COMMAND='install rails'" unless ENV['COMMAND']
      bundle_exec "gem #{ENV['COMMAND']}"
    end
    
    desc "Run a git command in COMMAND"
    task :git do
      raise "Specify the command with COMMAND. eg: COMMAND='diff HEAD^^'" unless ENV['COMMAND']
      run "cd #{current_path} && /bin/git #{ENV['COMMAND']}"
    end

    namespace :bundle do
      desc "Execute a COMMAND with bundle exec"
      task :default do
        raise "No Gemfile detected" unless gemfile_present?
        raise "Specify the command with COMMAND. eg: COMMAND='rake db:migrate'" unless ENV['COMMAND']
        bundle_exec ENV['COMMAND']
      end

      desc <<-DESC
        Install the current Bundler environment. By default, gems will be \
        installed to the shared/bundle path. Gems in the development and \
        test group will not be installed. The install command is executed \
        with the --deployment and --quiet flags. If the bundle cmd cannot \
        be found then you can override the bundle_cmd variable to specifiy \
        which one it should use.

        You can override any of these defaults by setting the variables shown below.

        N.B. bundle_roles must be defined before you require 'bundler/capistrano' \
        in your deploy.rb file.

          set :bundle_dir,      File.join(fetch(:shared_path), 'bundle')
          set :bundle_flags,    "--deployment --quiet"
          set :bundle_without,  [:development, :test]
          set :bundle_roles,    :app # e.g. [:app, :batch]
      DESC
      task :install do
        raise "No Gemfile detected" unless gemfile_present?
        bundle_flags   = fetch(:bundle_flags, "--deployment --quiet")
        bundle_dir     = '../bundle'
        bundle_without = [*fetch(:bundle_without, [:development, :test])].compact
        current_release = fetch(:current_path)
        if current_release.to_s.empty?
          raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
        end
        args = [bundle_flags.to_s]
        args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
        args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?

        run_ruby_cmd "bundle install #{args.join(' ')}"
      end
    end

    after 'deploy:setup', 'deploy:update_code'
    after 'deploy:setup', 'deploy:mongrel:setup'

  end
else
  # Call this method in a stage's configuration file (eg: deploy/staging.rb)
  # if that deploy target is not a Windows server.
  # This method is a no-op, but if that method is called from a stage recipe,
  # the capistrano-windows-server code does not load for that target.
  # Use this when some of your servers are Windows and some are Linux, etc.
  def not_windows!
  end
end
